import sys
import cv2 as cv
import numpy as np
import math
import random
import numpy as np
import os
import cv2

# argv[1]
in_bin_path = sys.argv[1]
# argv[2]
out_video_path = sys.argv[2]

max_time = int(sys.argv[3])


def mkdir(path):
    folder = os.path.exists(path)

    if not folder:  # 判断是否存在文件夹如果不存在则创建为文件夹
        os.makedirs(path)  # makedirs 创建文件时如果路径不存在会创建这个路径
        print('---  new folder...  ---')
        print("---  OK  ---")
    else:
        print("---  There is this folder!  ---")
    return path


# # argv[1]
# in_bin_path = '2.bin'
# # argv[2]
# out_video_path = '2.avi'


pics_path = mkdir('pics/')





# 规定参数
myfilesize = 200000  # bytes
mywidth = 400
myheight = 225
myunitsize = 8300
myfps = 12

mythickness = 5
myexpandedrate = 6
pic_width = (mywidth + 2 * mythickness) * myexpandedrate
pic_height = (myheight + 2 * mythickness) * myexpandedrate

bigwe = 2
pSize = 15
aSize = 15
smallwe = 2
# 480 -> unitsize = 12000
# 400 -> unitsize = 8300
# 320->unitsize=5200
# 160 ->1250





file_size = int(myfilesize / 2)

# combination = b''
# for i in range(file_size):
#     combination += np.random.randint(100, 65536).to_bytes(2, 'big')
#
# with open(in_bin_path, "wb") as fp:
#     fp.write(combination)

# read the file & save it to binary_data
# open the file in binary
f = open(in_bin_path, "rb")

f.seek(0, 2)
eof = f.tell()
f.seek(0, 0)

unit_size = myunitsize
count = 1
# it indicates the number of bytes in a unit
# so that each qrcode has the same size
binary_data = []
while True:
    unit_data = f.read(unit_size)
    # debug & display
    print(str(count) + ": " + str(unit_data))
    count += 1
    binary_data.append(unit_data)
    if f.tell() >= eof:
        break

f.close()


# version:              0.5
# last updated in:      2023.4.2
# coding:               utf-8


# def GenerateRandomBytes(bits: int):
#     # 生成很多个十六位的二进制数字然后拼在一起
#     ans = b''
#     for _ in range(bits // 16):
#         ans += np.random.randint(1000, 64536).to_bytes(2, 'big')
#     return ans


def bytes_to_numpy(data):
    ans = []
    for byte in data:
        n0 = 1 if (byte & 0x01) == 0x01 else 0
        n1 = 1 if (byte & 0x02) == 0x02 else 0
        n2 = 1 if (byte & 0x04) == 0x04 else 0
        n3 = 1 if (byte & 0x08) == 0x08 else 0
        n4 = 1 if (byte & 0x10) == 0x10 else 0
        n5 = 1 if (byte & 0x20) == 0x20 else 0
        n6 = 1 if (byte & 0x40) == 0x40 else 0
        n7 = 1 if (byte & 0x80) == 0x80 else 0
        ans += [n7, n6, n5, n4, n3, n2, n1, n0]
    return np.array(ans, dtype=np.int32)


def int_to_bits(n, length):
    ans = []
    while length and n > 0:
        ans = [n % 2] + ans
        n //= 2
        length -= 1
    ans = [0] * length + ans
    return np.array(ans, dtype=np.int32)


def GetLenForValid(total_length):
    # total_length - k <= 2**k - 1
    k = math.ceil(math.log(total_length + 1, 2))
    while True:
        if 2 ** k + k < total_length + 1:
            k += 1
            break
        k -= 1
    return k


def binary_to_decimal(binary):
    """
    Converts a binary number to decimal.
    """
    decimal = 0
    for digit in binary:
        decimal = decimal * 2 + int(digit)
    return decimal


def list_to_str(lst):
    """
    Converts a list to a string.
    """
    return ''.join(map(str, lst))


def str_to_int_list(s):
    """
    Converts a string to a list of integers.
    """
    return [int(c) for c in s]


### QuickTransmissionCode.max_bits 暂时弃用
class QuickTransmissionCode:
    def __init__(self,
                 height=260,
                 width=470,
                 pSize=25,
                 pDi=4,
                 bigwe=3,
                 aSize=15,
                 aDi=3,
                 smallwe=2,
                 thickness=5,
                 expanding_scale=4,
                 len_data_hamming=16,
                 no=0,
                 useMask=False,
                 Debug=False):
        '''
            Args:
                height              integer       整个图像的高
                width               integer       整个图像的宽
                pSize               integer       大定位点的边长
                pDi                 integer       大定位点各个回字的间距
                bigwe               integer       大两种定位点周围白边的宽度
                aSize               integer       小定位点的边长
                aDi                 integer       小定位点各个回字的间距
                smallwe             integer       小两种定位点周围白边的宽度
                thickness           integer       整个图像周围白边的宽度
                expanding_scale     integer       图像放大的倍数
                len_hamming         integer       海明编码前每组的长度
                useMask             boolean       是否采用掩码
                Debug               boolean       是否启用Debug模式
        '''
        self.height = height
        self.width = width
        self.pSize = pSize
        self.pDi = pDi
        self.bigwe = bigwe
        self.aSize = aSize
        self.aDi = aDi
        self.smallwe = smallwe
        self.thickness = thickness
        self.expanding_scale = expanding_scale
        self.no = no
        self.useMask = useMask
        self.Debug = Debug
        self.total_bits = height * width - 3 * (pSize + bigwe) ** 2 - (aSize + smallwe) ** 2
        self.len_for_valid = GetLenForValid(self.total_bits)  # 用多少位来表示有效数据的长度
        self.dataLen = len_data_hamming  # 每组原始数据的长度
        self.r = 0  # 校验位的数量
        self.HammingLen = 0  # 海明码的长度
        self.max_bits = 0  # 整个图像能接受多少位数据
        self.initData()  # 初始化 r 与 HammingLen
        print(self.len_for_valid)

    def initData(self):
        self.r = 0
        while pow(2, self.r) - self.r < self.dataLen + 1:
            self.r += 1
        self.HammingLen = self.r + self.dataLen  # 求出插入校验码后的总长

        # 计算最大能接受多少数据
        l_es = round(self.total_bits / (self.dataLen + self.r) * self.dataLen - self.len_for_valid)
        for l_p in range(l_es + self.dataLen, l_es - self.dataLen - 1, -1):
            if (self.dataLen + self.r) * math.ceil((l_p + self.len_for_valid) / self.dataLen) <= self.total_bits:
                self.max_bits = l_p
                break

        print("图片能接受的最大数据位数:", self.max_bits)
        return

    def hammingEncode(self, data):
        HammingData = [1] * self.HammingLen
        for i in range(self.r):
            HammingData[pow(2, i) - 1] = 0  # 先默认校验位都设置为0
        dataIndex = 0
        for i in range(self.HammingLen):  # 插入数据
            if HammingData[i] == 1:
                HammingData[i] = data[dataIndex]
                dataIndex += 1

        for i in range(1, self.r + 1):  # 逐个计算校验位
            # pn所在位置
            pos = pow(2, (i - 1)) - 1
            Pn = 0
            for j in range(1, self.HammingLen + 1):
                if j & (2 ** (i - 1)) == (2 ** (i - 1)):
                    Pn ^= HammingData[j - 1]
            HammingData[pos] = Pn
        return HammingData

    def hammingDecode(self, HammingData_):
        HammingData = HammingData_.copy()
        G = [0] * (self.HammingLen - self.dataLen)
        for i in range(1, self.r + 1):  # 逐个计算校验位
            # pn所在位置
            pos = pow(2, (i - 1)) - 1
            Gn = 0
            # print('参与异或的Hi：',i,'组')
            for j in range(1, self.HammingLen + 1):
                if j & (2 ** (i - 1)) == (2 ** (i - 1)):
                    # print(j)
                    Gn ^= HammingData[j - 1]
            print('G', i, ':', HammingData[pos])

            if Gn == 1:
                G[i - 1] = 1
                # wrongList.append(i)#记下哪个校验组错了
        G = G[::-1]
        decimal_G = binary_to_decimal(list_to_str(G))
        print('list_to_str(G):', list_to_str(G))
        print('decimal_G:', decimal_G)
        HammingData[decimal_G - 1] ^= 1
        return HammingData

    def extractData(self, encoded):
        chouQv = []
        for i in range(len(encoded)):
            skip = False
            for j in range(5):
                if 2 ** j > len(encoded):
                    continue
                if 2 ** j - 1 == i:
                    skip = True
            if not skip:
                chouQv.append(encoded[i])
        return chouQv

    def HammingEncodeTotal(self, ans, n):
        """
        param: ans是未海明编码的一大串数据，n是指定海明码分组长度
        function：把一大串数据分组，生成海明码，再拼接
        return：生成的海明码数据
        """
        print("参与海明编码的位数（有效数据位数+存长度的位数）:", len(ans))
        ret = []
        data_groups = np.array_split(ans, [i for i in range(n, len(ans), n)])
        print('（海明码）数据分组数:', len(data_groups))
        for data in data_groups:
            if n > len(data):
                data = np.concatenate((data, np.zeros(n - len(data), dtype=int)), axis=0)  #
            ret += self.hammingEncode(data)
        return np.array(ret, dtype=int)

    def DrawPositionPattern_(self, img):  # 定位方块  # in-place
        pdPattern = np.array([[0] * self.pSize for _ in range(self.pDi)]
                             + [[0] * self.pDi + [1] * (self.pSize - 2 * self.pDi) + [0] * self.pDi for _ in
                                range(self.pDi)]
                             + [[0] * self.pDi + [1] * self.pDi + [0] * (self.pSize - 4 * self.pDi) + [1] * self.pDi + [
            0] * self.pDi for _ in range(self.pSize - 4 * self.pDi)]
                             + [[0] * self.pDi + [1] * (self.pSize - 2 * self.pDi) + [0] * self.pDi for _ in
                                range(self.pDi)]
                             + [[0] * self.pSize for _ in range(self.pDi)])
        # 三个角落
        img[0: self.pSize, 0: self.pSize] = pdPattern
        img[0: self.pSize, -self.pSize::] = pdPattern
        img[-self.pSize::, 0: self.pSize] = pdPattern

    def DrawAlignmentPattern_(self, img):
        aPattern = np.array([[0] * self.aSize for _ in range(self.aDi)]
                            + [[0] * self.aDi + [1] * (self.aSize - 2 * self.aDi) + [0] * self.aDi for _ in
                               range(self.aDi)]
                            + [[0] * self.aDi + [1] * self.aDi + [0] * (self.aSize - 4 * self.aDi) + [1] * self.aDi + [
            0] * self.aDi for _ in range(self.aSize - 4 * self.aDi)]
                            + [[0] * self.aDi + [1] * (self.aSize - 2 * self.aDi) + [0] * self.aDi for _ in
                               range(self.aDi)]
                            + [[0] * self.aSize for _ in range(self.aDi)])

        img[-self.aSize::, -self.aSize::] = aPattern

    def DrawWhiteEdgeForP_(self, img):
        we1 = np.ones(shape=(self.pSize, self.bigwe), dtype=np.int32)
        we2 = np.ones(shape=(self.bigwe, self.pSize + self.bigwe), dtype=np.int32)

        img[0: self.pSize, self.pSize: self.pSize + self.bigwe] = we1
        img[self.pSize: self.pSize + self.bigwe, 0: self.pSize + self.bigwe] = we2

        img[0: self.pSize, -self.pSize - self.bigwe: -self.pSize] = we1
        img[self.pSize: self.pSize + self.bigwe, -self.pSize - self.bigwe::] = we2

        img[-self.pSize::, self.pSize: self.pSize + self.bigwe] = we1
        img[-self.pSize - self.bigwe: -self.pSize, 0: self.pSize + self.bigwe] = we2

        img[-self.aSize::, -self.aSize - self.smallwe: -self.aSize] = np.ones((self.aSize, self.smallwe),
                                                                              dtype=np.int32)
        img[-self.aSize - self.smallwe: -self.aSize, (-self.aSize - self.smallwe)::] = np.ones(
            (self.smallwe, self.aSize + self.smallwe), dtype=np.int32)

    def Padding_(self, data):
        padding_b_len = self.total_bits - len(data)
        padding_b = b''
        for _ in range(padding_b_len // 16):
            padding_b += b'\xEC\x11'
        # 剩下的位数不够2个字节直接cat上去
        return np.concatenate((bytes_to_numpy(padding_b), bytes_to_numpy(b'\xEC\x11')[:padding_b_len % 16]), axis=0)

    def DataProcess(self, data):
        '''
            数据处理的流程参见 dataprocess.png
        '''
        ans = bytes_to_numpy(data)
        # if len(ans) > self.max_bits:  # 数据溢出则报错
        #   raise ValueError("输入数据过多，一张图片装不下，请减小数据长度至" + str(self.max_bits) + "位以下")
        ValidLength_b = int_to_bits(len(ans), length=self.len_for_valid)  # 至少要len_for_valid位才能表示有效数据的长度
        ans = np.concatenate((ValidLength_b, ans), axis=0)
        no_b = int_to_bits(self.no, length=8)
        ans = np.concatenate((no_b, ans), axis=0)
        ans = self.HammingEncodeTotal(ans, self.dataLen)
        padding_b = self.Padding_(ans)
        ans = np.concatenate((ans, padding_b), axis=0)
        return ans

    def FillData_(self, img, data):  # 填充数据
        height, width = img.shape
        data = self.DataProcess(data)
        if self.Debug:
            x = ""
            for i in range(self.total_bits):
                x += str(data[i])
            with open("a.txt", "w") as f:
                f.write(x)
        k = 0
        for j in range(width - 1, -1, -2):
            if j % 4 == (self.width - 1) % 4:
                for i in range(height - 1, -1, -1):
                    if img[i, j] == -1:
                        img[i, j] = data[k]
                        k += 1
                    if img[i, j - 1] == -1:
                        img[i, j - 1] = data[k]
                        k += 1
            else:
                for i in range(0, height, 1):
                    if img[i, j] == -1:
                        img[i, j] = data[k]
                        k += 1
                    if img[i, j - 1] == -1:
                        img[i, j - 1] = data[k]
                        k += 1
        assert k == self.total_bits

    def Mask(self, img):  # 掩码
        height, width = img.shape
        ans = img

        def mask8(i, j):
            return ((i * j) % 3 + (i + j) % 2) % 2 == 0

        limited = np.zeros((height, width), dtype=np.int32)
        a1 = self.pSize + self.bigwe
        a2 = self.aSize + self.smallwe
        limited[0: a1, 0: a1],
        limited[0: a1, -a1::],
        limited[-a1::, 0: a1],
        limited[-a2::, -a2::] \
            = \
            np.ones((a1, a1), np.int32),
        np.ones((a1, a1), np.int32),
        np.ones((a1, a1), np.int32),
        np.ones((a2, a2), np.int32)

        for i in range(height):
            for j in range(width):
                if mask8(i, j) and not limited[i, j]:
                    ans[i, j] = 0 if ans[i, j] == 1 else 1

        return ans

    def AddWhiteEdge(self, img, thickness):
        if thickness == 0:
            return img
        height, width = img.shape
        ans = np.ones(shape=(height + 2 * thickness, width + 2 * thickness), dtype=np.int32)
        ans[thickness: -thickness, thickness: -thickness] = img
        return ans

    def ExpandImage(self, img, height, width, expanding_scale):  # 放大图片
        expanded_img = np.zeros((height * expanding_scale, width * expanding_scale), dtype=np.int32)
        for i in range(height):
            for j in range(width):
                if img[i, j] == 1:
                    expanded_img[expanding_scale * i: expanding_scale * (i + 1),
                    expanding_scale * j: expanding_scale * (j + 1)] \
                        = \
                        np.ones((expanding_scale, expanding_scale), dtype=np.int32)
        return expanded_img

    def ReplaceValue_(self, img):
        # 所有1都替换为255
        img[img == 1] = 255

    def Encode(self, data, file_path):
        # Prepare data and arguments

        # print("图像除定位块外还剩位数:", self.total_bits)

        # 传入的数据如果为None，则认为是随机数据
        print("有效数据位数:", len(data) * 8)

        img = np.ones((self.height, self.width), dtype=np.int32)
        img = -img  # 所有值都赋为-1

        self.DrawPositionPattern_(img)
        self.DrawAlignmentPattern_(img)
        self.DrawWhiteEdgeForP_(img)
        self.FillData_(img, data)
        if self.useMask:
            img = self.Mask(img)
        if self.Debug:
            print(img)
            x = ""
            for i in range(self.height):
                for j in range(self.width):
                    x += str(img[i, j])
            print(x)

        img = self.AddWhiteEdge(img, self.thickness)
        expanded_img = self.ExpandImage(img, self.height + 2 * self.thickness,
                                        self.width + 2 * self.thickness, self.expanding_scale)
        self.ReplaceValue_(expanded_img)

        # End
        cv.imwrite(file_path, expanded_img)


count = 0

code = QuickTransmissionCode(
    height=myheight,
    width=mywidth,
    pSize=pSize,
    pDi=3,
    bigwe=bigwe,
    aSize=aSize,
    aDi=3,
    smallwe=smallwe,
    thickness=mythickness,
    expanding_scale=myexpandedrate,
    no=0,
    len_data_hamming=16,
    useMask=False,
    Debug=False
)

# set the start pic:
img_file = pics_path + str(count) + '.jpg'
code.no = 0
code.Encode(("<start>").encode(encoding="utf-8"), img_file)

for each in binary_data:
    count = count + 1

    if(count > myfps * max_time/1000):
        print('err!    video length is out of bound!')
        sys.exit()

    count_str = str(count)
    img_file = pics_path + count_str + '.jpg'
    code.no = count
    code.Encode(each, img_file)

# set the end pic:
for i in range(2):
    count = count + 1
    img_file = pics_path + str(count) + '.jpg'
    code.no = 255
    code.Encode(("<end>").encode(encoding="utf-8"), img_file)

# debug
print("Success")

# path = pics_path

filelist = os.listdir(pics_path)
# sort the pics
get_key = lambda i: int(i.split('.')[0])
filelist = sorted(filelist, key=get_key)

fps = myfps
size = (pic_width, pic_height)
# 可以使用cv2.resize()进行修改



# 产生视频
video = cv2.VideoWriter(out_video_path, cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size)

for item in filelist:
    if item.endswith('.jpg'):
        item = pics_path + item
        img = cv2.imread(item)

        img = cv2.resize(img, size)
        # debug
        print(item)

        video.write(img)

video.release()
